﻿//-----------------------------------------------------------------------
// <copyright file="ToolManager.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Tool
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using TileTool.Common;
    using TileTool.Message;
    using TileTool.Tool;
    using TileTool.Tool.Message;

    /// <summary>
    /// Base implementation of the tool manager interface.
    /// </summary>
    public class ToolManager : IToolManager
    {
        #region Constants - Private

        /// <summary>
        /// Default max stack size.
        /// </summary>
        private const int DefaultStackSize = 32;

        #endregion

        #region Fields - Private

        /// <summary>
        /// Message router we will send messages on.
        /// </summary>
        private MessageRouter msgRouter;

        /// <summary>
        /// Stack of tools. Top of stack is .Count - 1.
        /// </summary>
        private IList<ITool> toolStack = new List<ITool>(DefaultStackSize);

        #endregion

        #region Constructor

        /// <summary>
        /// Construct the tool manager.
        /// </summary>
        /// <param name="createData">Creation data.</param>
        public ToolManager(ToolManagerCreateData createData)
        {
            CreateData.Validate(createData);

            this.msgRouter = createData.MessageRouter;
        }

        #endregion

        #region Events - Public

        /// <summary>
        /// Event triggered before a tool has Install() called on it.
        /// </summary>
        public event Action<ITool> Installed;

        /// <summary>
        /// Event triggered after a tool has Uninstall() called on it.
        /// </summary>
        public event Action<ITool> Uninstalled;

        #endregion

        /// <summary>
        /// Gets the message router messages should be sent on.
        /// </summary>
        public MessageRouter MessageRouter
        {
            get
            {
                return this.msgRouter;
            }
        }

        #region Methods - Public

        /// <summary>
        /// Test to see if the passed tool's type is in the tool stack.
        /// 
        /// Note, this does not test for the tool reference itself, but for the tool's type.
        /// </summary>
        /// <param name="tool">Tool type to look for in the tool stack.</param>
        /// <returns>Returns true if the tool type is contained in the tool stack.</returns>
        public virtual bool ContainsTool(ITool tool)
        {
            if (tool == null)
            {
                return false;
            }

            return this.GetToolTypeIndex(tool.GetType()) != -1;
        }

        /// <summary>
        /// Test to see if the passed tool type is in the tool stack.
        /// </summary>
        /// <param name="toolType">Tool type to look for in the stack.</param>
        /// <returns>Returns true if the tool type is contianed in the tool stack.</returns>
        public virtual bool ContainsTool(Type toolType)
        {
            if (toolType == null)
            {
                return false;
            }

            return this.GetToolTypeIndex(toolType) != -1;
        }

        /// <summary>
        /// Install the passed tool into the tool stack.
        /// 
        /// On successful install of a tool, a IToolInstalledMessage message must be sent.
        /// </summary>
        /// <param name="tool">Tool to isntall into the tool stack.</param>
        /// <returns>Returns true if the tool was installed or false if a tool of the passed
        /// type already exists in the stack.</returns>
        public virtual bool Install(ITool tool)
        {
            if (tool == null)
            {
                throw new ArgumentNullException("tool", "Tool must be non-null to install.");
            }

            if (this.ContainsTool(tool))
            {
                return false;
            }

            if (tool.Mode == ToolMode.Modal)
            {
                int modalIndex = this.GetModalToolIndex();

                if (modalIndex != -1)
                {
                    this.UninstallTool(modalIndex);
                }
            }

            this.InstallTool(tool);

            return true;
        }

        /// <summary>
        /// Uninstall the passed tool type from the tool stack.
        /// 
        /// On successful uninstall of a tool, a IToolUninstalledMessage message must be sent.
        /// </summary>
        /// <param name="toolType">Tool type to uninstall from the tool stack.</param>
        public virtual void Uninstall(Type toolType)
        {
            if (toolType == null)
            {
                throw new ArgumentNullException("toolType", "Tool type must be non-null.");
            }

            int index = this.GetToolTypeIndex(toolType);
            if (index == -1)
            {
                return;
            }

            this.UninstallTool(index);
        }

        /// <summary>
        /// Uninstall the active modal tool if there is one.
        /// </summary>
        public virtual void UninstallModal()
        {
            int modalIndex = this.GetModalToolIndex();
            if (modalIndex != -1)
            {
                this.UninstallTool(modalIndex);
            }
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Given a tool type, find its index in the tool stack.
        /// </summary>
        /// <param name="toolType">Type of tool to find.</param>
        /// <returns>Returns the index of the tool if found or -1 if it was not found.</returns>
        private int GetToolTypeIndex(Type toolType)
        {
            Debug.Assert(toolType != null, "Tool type must be non-null.");

            int count = this.toolStack.Count;

            for (int i = 0; i < count; ++i)
            {
                ITool tool = this.toolStack[i];

                if (tool.GetType() == toolType)
                {
                    return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Find the index of any modal tools that are currently in the stack.
        /// </summary>
        /// <returns>Returns the index of the modal tool if there is one or -1 if we don't have one.</returns>
        private int GetModalToolIndex()
        {
            int count = this.toolStack.Count;

            for (int i = 0; i < count; ++i)
            {
                ITool tool = this.toolStack[i];

                if (tool.Mode == ToolMode.Modal)
                {
                    return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Install the passed too into the tool stack.
        /// </summary>
        /// <param name="tool">Tool to install.</param>
        private void InstallTool(ITool tool)
        {
            this.toolStack.Add(tool);

            Action<ITool> installed = this.Installed;
            if (installed != null)
            {
                installed(tool);
            }

            tool.Install(this);

            ToolInstalledMessage msg = new ToolInstalledMessage();

            msg.Tool = tool;

            this.SendMessage(msg);
        }

        /// <summary>
        /// Uninstall the tool at the passed index.
        /// </summary>
        /// <param name="index">Index of the tool to install at.</param>
        private void UninstallTool(int index)
        {
            Debug.Assert(index >= 0 && index <= this.toolStack.Count, "index must be a valid array index.");

            ITool tool = this.toolStack[index];

            this.toolStack.RemoveAt(index);

            tool.Uninstall();

            Action<ITool> uninstalled = this.Uninstalled;
            if (uninstalled != null)
            {
                uninstalled(tool);
            }

            ToolUninstalledMessage msg = new ToolUninstalledMessage();

            msg.Tool = tool;

            this.SendMessage(msg);
        }

        /// <summary>
        /// Send a message.
        /// </summary>
        /// <param name="msg">Message to send.</param>
        private void SendMessage(MessageBase msg)
        {
            this.msgRouter.SendMessage(msg);
        }

        #endregion
    }
}
